package com.basker.pisces.domain.entity.meta;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.Element;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.annotation.CollectionField;
import com.basker.pisces.domain.ISupportEntityLimit;
import com.basker.pisces.domain.entity.data.IDataEntity;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.property.meta.PropertyCollection;
import com.basker.pisces.domain.property.meta.ReferenceProperty;
import com.basker.pisces.spring.SpringApplicationContext;

public abstract class AbstractEntity extends Element {

    protected volatile transient IDataObjectMeta dataObjectMeta;
    @CollectionField
    private PropertyCollection properties = new PropertyCollection(this);
    @CollectionField
    private ChildEntityCollection childEntities = new ChildEntityCollection(this);

    public AbstractEntity() {

    }

    public void addChildEntity(ChildEntity childEntity) {
        Assert.notNull(childEntity, "parameter 'childEntity' is required");

        this.childEntities.add(childEntity);
    }

    public void addProperty(Property<?> property) {
        Assert.notNull(property, "parameter 'property' is required");

        this.checkEntityLimit(property);
        this.properties.add(property);
    }

    public void fillMapObject(final IDataEntity dataEntity,
                              final Object dataObject,
                              final Map<String, Object> mapObject) {
        Object idValue = this.getIdValue(dataObject);
        if (idValue != null) {
            mapObject.put(IDataField.ID_FIELD_NAME, idValue);
        }
    }

    public ChildEntityCollection getChildEntities() {
        return childEntities;
    }

    public void setChildEntities(ChildEntityCollection childEntities) {
        this.childEntities = childEntities;
    }

    public ChildEntity getChildEntity(String entityName) {
        return EntityHelper.getChildEntity(this, entityName, false);
    }

    public ChildEntity getChildEntity(String entityName, boolean required) {
        return EntityHelper.getChildEntity(this, entityName, required);
    }

    public IDataObjectMeta getDataObjectMeta() {
        return dataObjectMeta;
    }

    public Object getIdValue(Object dataObject) {
        Assert.notNull(dataObject, "parameter 'dataObject' is required");

        return this.getDataObjectMeta().getPkField().getValue(dataObject);
    }

    public PropertyCollection getProperties() {
        return properties;
    }

    public void setProperties(PropertyCollection properties) {
        this.properties = properties;
    }

    public Property<?> getProperty(String propertyName) {
        return EntityHelper.getProperty(this, propertyName, false);
    }

    public Property<?> getProperty(String propertyName, boolean required) {
        return EntityHelper.getProperty(this, propertyName, required);
    }

    public boolean isNew(Object dataObject) {
        Assert.notNull(dataObject, "parameter 'dataObject' is required");

        return !this.getDataObjectMeta().isFromDB(dataObject);
    }

    @Override
    public String toString() {
        return "[" + super.toString() + "]";
    }

    protected void bindObjectMeta(IDataObjectMeta objectMeta) {
        for (Property<?> property : this.properties) {
            property.bindField(objectMeta);

            if (property instanceof ReferenceProperty) {
                this.loadReferenceEntity((ReferenceProperty) property);
            }
        }

        for (ChildEntity childEntity : this.childEntities) {
            childEntity.bindField(objectMeta);
            childEntity.bindObjectMeta(childEntity.getDataObjectMeta());
        }
    }

    protected void checkEntityLimit(Object component) {
        if (component instanceof ISupportEntityLimit) {
            Class<? extends Entity> limitEntiyType = ((ISupportEntityLimit) component).getLimitEntiyType();

            if (limitEntiyType != null
                    && !limitEntiyType.isAssignableFrom(EntityHelper.getRootEntity(this).getClass())) {
                throw new IllegalArgumentException(
                        "'" + component.getClass().getName() + "' can only be used in '" + limitEntiyType + "' entity");
            }
        }
    }

    protected Entity findReferenceRootEntity(ReferenceProperty referenceProperty) {
        String referenceEntityName = referenceProperty.getReferenceEntityName();
        if (StringUtils.isEmpty(referenceEntityName)) {
            return null;
        }

        referenceEntityName = StringUtils.uncapitalize(referenceEntityName);
        return SpringApplicationContext.getInstance().getBean(referenceEntityName, Entity.class);
    }

    protected abstract Entity findRoot();

    protected void onCopy(AbstractEntity copyEntity) {
        for (Property<?> property : this.properties) {
            Property<?> copyProperty = copyEntity.getProperty(property.getName());

            property.onCopy(copyProperty);
        }

        for (ChildEntity childEntity : this.childEntities) {
            ChildEntity copyChildEntity = copyEntity.getChildEntity(childEntity.getName());

            childEntity.onCopy(copyChildEntity);
        }
    }

    /**
     * 加载引用属性关联的实体
     */
    private void loadReferenceEntity(ReferenceProperty referenceProperty) {
        Entity rootEntity = this.findReferenceRootEntity(referenceProperty);
        if (rootEntity != null) {
            List<String> propertyAliasList = referenceProperty.getReferenceObjectMeta().getFields().stream()
                    .map(f -> f.getName()).collect(Collectors.toList());
            Entity subEntity = rootEntity.getSubEntity(propertyAliasList);

            referenceProperty.setReferenceEntity(subEntity);
        }
    }
}
